package com.yonyou.pmclouds.storage;

import com.yonyou.pmclouds.basecom.conf.NCCStorageProperties;
import com.yonyou.pmclouds.basecom.exception.BusinessException;
import com.yonyou.pmclouds.basecom.exception.BusinessRuntimeException;
import com.yonyou.pmclouds.basecom.runtime.RuntimeEnvironment;
import com.yonyou.pmclouds.basecom.util.NCCFileStorageUtil;
import com.yonyou.pmclouds.basecom.util.StorageFactory;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RestController
public class StorageController {
    @Autowired
    private StorageFactory storageFactory;
    @Autowired
    private NCCStorageProperties nccStorageProperties;
    @Autowired
    private NCCFileStorageUtil nccFileStorageUtil;

    @GetMapping("/oss/signForPost")
    public Map<String, String> signForWebPost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        String dir = "app/" + RuntimeEnvironment.getTenantId() + "/";
        return storageFactory.getStorageUtil().getSignWithDir(dir, request);
    }

    @GetMapping("/mobile/oss/signForPost")
    public Map<String, String> signForAppPost(@RequestParam("tenantId") String tenantId, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        RuntimeEnvironment.setTenantId(tenantId);
        return this.signForWebPost(request, response);
    }

    @GetMapping("/oss/example/signForPost")
    public Map<String, String> signForWebExamplePost(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        String dir = "web/example/" + RuntimeEnvironment.getTenantId() + "/";
        return storageFactory.getStorageUtil().getSignWithDir(dir, request);
    }

    /**
     * * Notes:
     *      * 1). Capable for oss and ncc
     *      * 2). Similar functionality provided at /file/download
     * @param picProcess
     * @param request
     * @param response
     * @return
     * @throws UnsupportedEncodingException
     */
    @PostMapping(value = {"/mobile/oss/signForAppPic", "/oss/signForAppPic"})
    public List<String> signUrlForAppPic(@RequestBody PicProcess picProcess, HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
        if (ArrayUtils.isEmpty(picProcess.pics)) {
            return null;
        }
        List<String> signedUrl = new ArrayList<>(picProcess.pics.length);
        for (String pic : picProcess.pics) {
            signedUrl.add(storageFactory.getStorageUtil().signUrlForSingleAppPic(pic, picProcess.process, request));
        }

        return signedUrl;
    }

    @Setter
    @Getter
    private static class PicProcess {
        String[] pics;
        String process;
    }

    /**
     * Notes:
     * 1). For NC Storage Only
     * 2). Put it under /rest to get around the YHT cookie based authentication.
     * @param request
     * @param response
     * @throws IOException
     */
    @RequestMapping(NCCFileStorageUtil.FILE_URI + "/**/")
    public void getFile(@RequestParam(value = "ts", required = false) Long timestamp, @RequestParam(value = "tk", required = false) String token, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String path = request.getRequestURI().replaceFirst(NCCFileStorageUtil.FILE_URI+"/?","");
        if(path == null || path.length() == 0)
            throw new BusinessRuntimeException("File path is required.");

        /* As the 'filename' part in the origin path could be encoded, but the folder part didn't. Decode first then encode,
         * to avoid double encoding for filename, and make sure folder and filename are both encoded.  */
        path = URLDecoder.decode(path, "utf-8").replace("%20", "+");

        if(nccStorageProperties.getValidateToken())
            validateRequest(path, timestamp, token);

        path = URLEncoder.encode(path, "utf-8").replace("+","%20");
        String url = nccStorageProperties.getEndpoint() + "/pmbd/pmcloud/fileStorage?ds=" + nccStorageProperties.getDatasource() + "&path=" + path;

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = null;
        InputStream is = null;
        ServletOutputStream sos = null;

        try {
            httpGet = new HttpGet(url);
            CloseableHttpResponse getResponse = httpClient.execute(httpGet);

            is = getResponse.getEntity().getContent();
            sos = response.getOutputStream();

            Integer bufferSize = nccStorageProperties.getBufferSize();
            if(bufferSize == null)
                bufferSize = 1024;

            byte[] b = new byte[bufferSize];
            int len = -1;
            while((len = is.read(b)) != -1)
                sos.write(b, 0, len);

            sos.flush();
        }finally {
            if(sos != null)
                sos.close();
            if(is != null)
                is.close();
        }
    }

    /**
     * 获取图片不需要登录， 故对图片地址进行签名验证：
     * １）．　确保签名可用
     * ２）．　签发链接时效为１天
     * @param path
     * @param ts
     * @param token
     */
    private void validateRequest(String path, Long ts, String token){
        if(token == null || ts == null)
            throw new BusinessRuntimeException("Invalid request: missing required value.");

         if(!token.equals(nccFileStorageUtil.sign(path, ts)))
            throw new BusinessRuntimeException("Invalid request: token info is invalid.");

        if(System.currentTimeMillis() - ts > 1000 * 60 * 60 * 24)
            throw new BusinessRuntimeException("Invalid request: link expired.");
    }

    /**
     * Notes:
     * 1). For NC Storaget Only
     * @param file
     * @return
     * @throws BusinessException
     * @throws IOException
     */
    @RequestMapping("/file/upload")
    public Object upload(@RequestParam("file") MultipartFile file, @RequestParam("key") String path) throws BusinessException, IOException {
        String url = nccStorageProperties.getEndpoint() + "/pmbd/pmcloud/fileStorage?ds=" + nccStorageProperties.getDatasource();

        if(path == null)
            path = file.getOriginalFilename();

        path = URLEncoder.encode(path, "UTF-8");

        InputStream is = null;
        try {
            is=file.getInputStream();

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();
            builder.setCharset(Charset.forName("utf-8"));
            builder.addBinaryBody("file", is, ContentType.create("multipart/form-data", "utf-8"), path);

            org.apache.http.HttpEntity entity = builder.build();
            HttpPost httpPost = new HttpPost(url);
            httpPost.setEntity(entity);

            CloseableHttpClient httpClient = HttpClients.createDefault();
            HttpResponse response = httpClient.execute(httpPost);
            return EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8"));
        }finally {
            if(is != null) {
                is.close();
            }
        }
    }
}
